package cn.donting.jme.core.state;

import cn.donting.jme.JmeApplication;
import cn.donting.jme.core.input.InputRaw;
import cn.donting.jme.ioc.Bean;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.input.CameraInput;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;

/**
 * @see com.jme3.input.FlyByCamera
 * @see com.jme3.app.FlyCamAppState
 */
@Bean
public class EditorCameraMoveState extends AbstractEditorAppState implements AnalogListener {
    private AppStateManager stateManager;
    private Application app;
    private float moveSpeed = 10f;
    private float rotationSpeed = 3f;
    private Camera camera;
    private boolean invertY;
    protected Vector3f initialUpVec;

    private boolean canRotate = false;

    @Override
    public void initialize(AppStateManager stateManager, JmeApplication app) {
        this.stateManager = stateManager;
        this.app = app;
        this.camera = app.getEditorCamera();
        InputManager inputManager = app.getInputManager();
        // both mouse and button - rotation of cam
        inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true),
                new KeyTrigger(KeyInput.KEY_LEFT));

        inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false),
                new KeyTrigger(KeyInput.KEY_RIGHT));

        inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false),
                new KeyTrigger(KeyInput.KEY_UP));

        inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true),
                new KeyTrigger(KeyInput.KEY_DOWN));

        inputManager.addListener(this,
                CameraInput.FLYCAM_LEFT,
                CameraInput.FLYCAM_RIGHT,
                CameraInput.FLYCAM_UP,
                CameraInput.FLYCAM_DOWN);
        initialUpVec = app.getEditorCamera().getUp().clone();
    }


    @Override
    public void update(float tpf) {
        if (InputRaw.mouseButtonDown(MouseInput.BUTTON_RIGHT)) {
            app.getInputManager().setCursorVisible(false);
            canRotate = true;
        }
        if (InputRaw.mouseButtonUp(MouseInput.BUTTON_RIGHT)) {
            canRotate = false;
            app.getInputManager().setCursorVisible(true);
        }
        if (InputRaw.keyPress(KeyInput.KEY_W)) {
            moveCamera(tpf, false);
        }
        if (InputRaw.keyPress(KeyInput.KEY_S)) {
            moveCamera(-tpf, false);
        }
        if (InputRaw.keyPress(KeyInput.KEY_A)) {
            moveCamera(tpf, true);
        }
        if (InputRaw.keyPress(KeyInput.KEY_D)) {
            moveCamera(-tpf, true);
        }
        if (InputRaw.keyPress(KeyInput.KEY_Z)) {
            riseCamera(-tpf);
        }
        if (InputRaw.keyPress(KeyInput.KEY_Q)) {
            riseCamera(tpf);
        }
    }

    /**
     * 基于某个轴旋转 ，世界轴
     *
     * @param value
     * @param axis
     */
    protected void rotateCamera(float value, Vector3f axis) {
        if (!canRotate) {
            return;
        }
        Matrix3f mat = new Matrix3f();
        mat.fromAngleNormalAxis(rotationSpeed * value, axis);

        Vector3f up = camera.getUp();
        Vector3f left = camera.getLeft();
        Vector3f dir = camera.getDirection();

        mat.mult(up, up);
        mat.mult(left, left);
        mat.mult(dir, dir);

        Quaternion q = new Quaternion();
        q.fromAxes(left, up, dir);
        q.normalizeLocal();

        camera.setAxes(q);
    }

    protected void moveCamera(float value, boolean sideways) {
        Vector3f vel = new Vector3f();
        Vector3f pos = camera.getLocation().clone();

        if (sideways) {
            camera.getLeft(vel);
        } else {
            camera.getDirection(vel);
        }
        vel.multLocal(value * moveSpeed);
        pos.addLocal(vel);

        camera.setLocation(pos);
    }

    protected void riseCamera(float value) {
        Vector3f vel = initialUpVec.mult(value * moveSpeed);
        Vector3f pos = camera.getLocation().clone();
        pos.addLocal(vel);
        camera.setLocation(pos);
    }

    @Override
    public void onAnalog(String name, float value, float tpf) {
        if (!isEnabled())
            return;
        if (name.equals(CameraInput.FLYCAM_LEFT)) {
            rotateCamera(value, Vector3f.UNIT_Y);
        } else if (name.equals(CameraInput.FLYCAM_RIGHT)) {
            rotateCamera(-value, Vector3f.UNIT_Y);
        } else if (name.equals(CameraInput.FLYCAM_UP)) {
            rotateCamera(-value * (invertY ? -1 : 1), camera.getLeft());
        } else if (name.equals(CameraInput.FLYCAM_DOWN)) {
            rotateCamera(value * (invertY ? -1 : 1), camera.getLeft());
        }
    }
}
